package org.optionparser;

import junit.framework.TestCase;
import org.junit.Test;

/**
 * Unit test for class OptionTest
 * 
 * @author Jakub Michalko
 * 
 */
public class OptionTest extends TestCase {

	// ---------------------------------------
	// short/long names tests
	// ---------------------------------------

	/**
	 * Function for checking, if string is null
	 * 
	 * @param text
	 *            checked string
	 * @return true if text is null, otherwise return false
	 */
	private static boolean isNullString(String text) {
		if (text == null) {
			return true;
		}
		return false;
	}

	/**
	 * Function for comparing strings.
	 * 
	 * @param textA
	 *            first string
	 * @param textB
	 *            second string
	 * @return true, if strings are equals of both strings are null, otherwise
	 *         return false
	 */
	private static boolean stringEquals(String textA, String textB) {
		if (isNullString(textA) && isNullString(textB)) {
			// both strings are null
			return true;
		}
		if (isNullString(textA) || isNullString(textB)) {
			// one string is null, but the other is not
			return false;
		}
		return textA.equals(textB);

	}

	/**
	 * Test methods for retrieving canonical names.
	 */
	@Test
	public void testCanonicalNames() {
		String[] inputLongNames = { "version", "he_ lp", "notice", null, "" };
		String[] resultLongNames = { "version", "he_ lp", "notice", "", "" };

		String[] inputShortNames = { "v", "h", "n", "0", null, "" };
		String[] resultShortNames = { "v", "h", "n", "0", "", "" };

		int maxLongIndex = Math.min(inputLongNames.length,
				resultLongNames.length);
		int maxShortIndex = Math.min(inputShortNames.length,
				resultShortNames.length);

		// test all combinations of short and long names

		for (int shortNameIndex = 0; shortNameIndex < maxShortIndex; shortNameIndex++) {
			for (int longNameIndex = 0; longNameIndex < maxLongIndex; longNameIndex++) {

				String shortName = inputShortNames[shortNameIndex];
				String longName = inputLongNames[longNameIndex];
				Option opt = new Option(shortName, longName,
						OptionType.Optional, ParameterType.Forbidden,
						ParameterClass.None, "version", null, 100, null,
						"nejaky popis");

				assertTrue(
						"Canonical short name \"" + shortName
								+ "\" name was not set correctly",
						stringEquals(resultShortNames[shortNameIndex],
								opt.getCanonicalShortName()));
				assertTrue(
						"Canonical long name \"" + longName
								+ "\" was not set correctly (expecting "
								+ resultLongNames[longNameIndex] + ")",
						stringEquals(resultLongNames[longNameIndex],
								opt.getCanonicalLongName()));
			}

		}
	}

	/**
	 * constants for creating default option
	 */

	private static String DEFAULT_SHORT_NAME = "";
	private static String DEFAULT_LONG_NAME = "";
	private static OptionType DEFAULT_OPTION_TYPE = OptionType.Optional;
	private static ParameterType DEFAULT_PARAMETER_TYPE = ParameterType.Optional;
	private static ParameterClass DEFAULT_PARAMETER_CLASS = ParameterClass.None;
	private static String DEFAULT_OPTION_NAME = "defaultoption";
	private static int DEFAULT_LOWER_BOUNDARY = 0;
	private static int DEFAULT_UPPER_BOUNDARY = 100;
	private static String[] DEFUALT_EXPECTED_VALUES = null;
	private static String DEFAULT_DESCRIPTION = "Default description";

	/**
	 * Test methods that retrieves option names with prefix "-" and "--"
	 */
	@Test
	public void testOptionNames() {
		String[] inputLongNames = { "version", "help", "notice", null };
		String[] resultLongNames = { "--version", "--help", "--notice", "--" };

		String[] inputShortNames = { "v", "h", "n", "0", "" };
		String[] resultShortNames = { "-v", "-h", "-n", "-0", "-" };

		int maxLongIndex = Math.min(inputLongNames.length,
				resultLongNames.length);
		int maxShortIndex = Math.min(inputShortNames.length,
				resultShortNames.length);
		// test all combinations
		for (int shortNameIndex = 0; shortNameIndex < maxShortIndex; shortNameIndex++) {
			for (int longNameIndex = 0; longNameIndex < maxLongIndex; longNameIndex++) {

				String shortName = inputShortNames[shortNameIndex];
				String longName = inputLongNames[longNameIndex];
				Option opt = new Option(shortName, longName,
						DEFAULT_OPTION_TYPE, DEFAULT_PARAMETER_TYPE,
						DEFAULT_PARAMETER_CLASS, DEFAULT_OPTION_NAME,
						DEFAULT_LOWER_BOUNDARY, DEFAULT_UPPER_BOUNDARY,
						DEFUALT_EXPECTED_VALUES, DEFAULT_DESCRIPTION);

				assertTrue(
						"Short name \"" + shortName
								+ "\" name was not set correctly",
						stringEquals(resultShortNames[shortNameIndex],
								opt.getShortName()));
				assertTrue(
						"Long name \"" + longName + "\" was not set correctly ",
						stringEquals(resultLongNames[longNameIndex],
								opt.getLongName()));

			}
		}

	}

	/**
	 * Test methods toString() that retrieves option names with prefix "-" and
	 * "--"
	 */
	@Test
	public void testToString() {
		String[] inputLongNames = { "version", "help", null, "", null, "", null };
		String[] inputShortNames = { null, "h", "n", "x", "", "", null };
		String[] resultString = { "--version", "--help", "-n", "-x", "-", "-",
				"-" };

		int maxInndex = Math.min(inputLongNames.length, inputShortNames.length);
		maxInndex = Math.min(maxInndex, resultString.length);

		// test defined combinations
		for (int nameIndex = 0; nameIndex < maxInndex; nameIndex++) {

			String shortName = inputShortNames[nameIndex];
			String longName = inputLongNames[nameIndex];
			Option opt = new Option(shortName, longName, DEFAULT_OPTION_TYPE,
					DEFAULT_PARAMETER_TYPE, DEFAULT_PARAMETER_CLASS,
					DEFAULT_OPTION_NAME, DEFAULT_LOWER_BOUNDARY,
					DEFAULT_UPPER_BOUNDARY, DEFUALT_EXPECTED_VALUES,
					DEFAULT_DESCRIPTION);

			String errorMessage = String
					.format("Function toString() returns wrong result for shortName \"%s\" and long name \"%s\" ",
							shortName, longName);
			assertTrue(errorMessage,
					stringEquals(resultString[nameIndex], opt.toString()));

		}

	}

	/**
	 * Test methods toCanonicalString()
	 */
	@Test
	public void testToCanonicalString() {
		String[] inputLongNames = { "version", "help", null, "", null, "", null };
		String[] inputShortNames = { null, "h", "n", "x", "", "", null };
		String[] resultString = { "version", "help", "n", "x", "", "", "" };

		int maxInndex = Math.min(inputLongNames.length, inputShortNames.length);
		maxInndex = Math.min(maxInndex, resultString.length);

		// test defined combinations
		for (int nameIndex = 0; nameIndex < maxInndex; nameIndex++) {

			String shortName = inputShortNames[nameIndex];
			String longName = inputLongNames[nameIndex];
			Option opt = new Option(shortName, longName, DEFAULT_OPTION_TYPE,
					DEFAULT_PARAMETER_TYPE, DEFAULT_PARAMETER_CLASS,
					DEFAULT_OPTION_NAME, DEFAULT_LOWER_BOUNDARY,
					DEFAULT_UPPER_BOUNDARY, DEFUALT_EXPECTED_VALUES,
					DEFAULT_DESCRIPTION);

			String errorMessage = String
					.format("Function toCanonicalString() returns wrong result for shortName \"%s\" and long name \"%s\" ",
							shortName, longName);
			assertTrue(
					errorMessage,
					stringEquals(resultString[nameIndex],
							opt.toCanonicalString()));

		}

	}

	// ---------------------------------------
	//
	// ---------------------------------------

	/**
	 * Create default option
	 */
	private static Option createDefaultOption() {

		Option result = new Option(DEFAULT_SHORT_NAME, DEFAULT_LONG_NAME,
				DEFAULT_OPTION_TYPE, DEFAULT_PARAMETER_TYPE,
				DEFAULT_PARAMETER_CLASS, DEFAULT_OPTION_NAME,
				DEFAULT_LOWER_BOUNDARY, DEFAULT_UPPER_BOUNDARY,
				DEFUALT_EXPECTED_VALUES, DEFAULT_DESCRIPTION);
		return result;

	}

	private static String EMPTY_STRING = "";

	@Test
	public void valueTest() {

		String[] inputValues = { "123", "aBcD", "", null, "123abc456" };

		for (String inputValue : inputValues) {
			Option opt = createDefaultOption();
			opt.setValue(inputValue);

			assertTrue("Value" + inputValue + " was not set correctly ",
					stringEquals(inputValue, opt.getValue()));

		}

		Option noValueOption = createDefaultOption();
		assertTrue("Option undefined value is not empty string",
				stringEquals(EMPTY_STRING, noValueOption.getValue()));
	}

	/**
	 * Check if parameter type was set correctly
	 */
	@Test
	public void testParameterType() {
		for (ParameterType parameterType : ParameterType.values()) {
			Option option = new Option(DEFAULT_SHORT_NAME, DEFAULT_LONG_NAME,
					DEFAULT_OPTION_TYPE, parameterType,
					DEFAULT_PARAMETER_CLASS, DEFAULT_OPTION_NAME,
					DEFAULT_LOWER_BOUNDARY, DEFAULT_UPPER_BOUNDARY,
					DEFUALT_EXPECTED_VALUES, DEFAULT_DESCRIPTION);

			assertTrue("Parameter type" + parameterType
					+ " was not set correctly ",
					option.getParameterType() == parameterType);
		}

		Option nullParametertTypeOption = new Option(DEFAULT_SHORT_NAME,
				DEFAULT_LONG_NAME, DEFAULT_OPTION_TYPE, null,
				DEFAULT_PARAMETER_CLASS, DEFAULT_OPTION_NAME,
				DEFAULT_LOWER_BOUNDARY, DEFAULT_UPPER_BOUNDARY,
				DEFUALT_EXPECTED_VALUES, DEFAULT_DESCRIPTION);
		assertNull("Expected null parametert type",
				nullParametertTypeOption.getParameterType());

	}

	private static boolean isNullObject(Object text) {
		if (text == null) {
			return true;
		}
		return false;
	}

	/**
	 * Compare string arrays
	 * 
	 * @param stringArrayA
	 *            input array A
	 * @param stringArrayB
	 *            input array B
	 * @return true, if both arrays has the same string items on the same
	 *         positions or both arrays are null, otherwise return false
	 */
	private static boolean stringArraysAreEqual(String[] stringArrayA,
			String[] stringArrayB) {
		if (isNullObject(stringArrayA) && isNullObject(stringArrayB)) {
			// both arrays are null
			return true;
		}
		if (isNullObject(stringArrayA) || isNullObject(stringArrayB)) {
			// one array is null, but the other is not
			return false;
		}

		if (stringArrayA.length != stringArrayB.length) {
			// different array length
			return false;
		}

		// both arrays are the same size and not null
		for (int index = 0; index < stringArrayA.length; index++) {
			if (stringEquals(stringArrayA[index], stringArrayB[index]) == false) {
				return false;
			}
		}
		// both arrays has the same items
		return true;
	}

	/**
	 * Test expected values
	 */
	@Test
	public void testExpectedValues() {

		String[][] inputExpectedValuesData = { { "a" }, { "a", "b" }, null,
				{ "1", "2", "3", "4", "5" } };
		String[][] testExpectedValuesData = { { "a" }, { "a", "b" }, {},
				{ "1", "2", "3", "4", "5" } };
		int maxIndex = Math.min(inputExpectedValuesData.length,
				testExpectedValuesData.length);

		for (int stringArrayIndex = 0; stringArrayIndex < maxIndex; stringArrayIndex++) {
			String[] inputValues = inputExpectedValuesData[stringArrayIndex];
			Option option = new Option(DEFAULT_SHORT_NAME, DEFAULT_LONG_NAME,
					DEFAULT_OPTION_TYPE, DEFAULT_PARAMETER_TYPE,
					DEFAULT_PARAMETER_CLASS, DEFAULT_OPTION_NAME,
					DEFAULT_LOWER_BOUNDARY, DEFAULT_UPPER_BOUNDARY,
					inputValues, DEFAULT_DESCRIPTION);

			assertTrue(
					"Expected values was not set correctly",
					stringArraysAreEqual(option.getExpectedValues(),
							testExpectedValuesData[stringArrayIndex]));
		}

	}

	/**
	 * Test parameter class
	 */
	@Test
	public void testParameterClass() {
		for (ParameterClass parameterClass : ParameterClass.values()) {
			Option option = new Option(DEFAULT_SHORT_NAME, DEFAULT_LONG_NAME,
					DEFAULT_OPTION_TYPE, DEFAULT_PARAMETER_TYPE,
					parameterClass, DEFAULT_OPTION_NAME,
					DEFAULT_LOWER_BOUNDARY, DEFAULT_UPPER_BOUNDARY,
					DEFUALT_EXPECTED_VALUES, DEFAULT_DESCRIPTION);

			assertTrue("Parameter class" + parameterClass
					+ " was not set correctly ",
					option.getParameterClass() == parameterClass);
		}

		Option nullParametertClassOption = new Option(DEFAULT_SHORT_NAME,
				DEFAULT_LONG_NAME, DEFAULT_OPTION_TYPE, DEFAULT_PARAMETER_TYPE,
				null, DEFAULT_OPTION_NAME, DEFAULT_LOWER_BOUNDARY,
				DEFAULT_UPPER_BOUNDARY, DEFUALT_EXPECTED_VALUES,
				DEFAULT_DESCRIPTION);
		assertNull("Expected null parametert class",
				nullParametertClassOption.getParameterClass());
	}

	/**
	 * Test parameter name
	 */
	@Test
	public void testParameterName() {
		String[] testNames = { "1234", "name1", "name with spaces", null, "" };

		for (String testOptionName : testNames) {

			Option option = new Option(DEFAULT_SHORT_NAME, DEFAULT_LONG_NAME,
					DEFAULT_OPTION_TYPE, DEFAULT_PARAMETER_TYPE,
					DEFAULT_PARAMETER_CLASS, testOptionName,
					DEFAULT_LOWER_BOUNDARY, DEFAULT_UPPER_BOUNDARY,
					DEFUALT_EXPECTED_VALUES, DEFAULT_DESCRIPTION);
			assertTrue("Parameter name" + testOptionName
					+ " was not set correctly ",
					stringEquals(option.getParameterName(), testOptionName));
		}

	}

	/**
	 * Description test
	 */
	@Test
	public void testDescription() {

		String[] inputDescription = { "description", "description with spaces",
				"cammelCaseDescription", "1 23  4 5", null };
		String[] resultDescription = { "description",
				"description with spaces", "cammelCaseDescription",
				"1 23  4 5", "" };

		int maxIndex = Math.min(inputDescription.length,
				resultDescription.length);

		for (int descriptionIndex = 0; descriptionIndex < maxIndex; descriptionIndex++) {

			String description = inputDescription[descriptionIndex];
			Option opt = new Option(DEFAULT_SHORT_NAME, DEFAULT_LONG_NAME,
					DEFAULT_OPTION_TYPE, DEFAULT_PARAMETER_TYPE,
					DEFAULT_PARAMETER_CLASS, DEFAULT_OPTION_NAME,
					DEFAULT_LOWER_BOUNDARY, DEFAULT_UPPER_BOUNDARY,
					DEFUALT_EXPECTED_VALUES, description);

			assertTrue(
					"Description \"" + description
							+ "\" was not set correctly ",
					stringEquals(resultDescription[descriptionIndex],
							opt.getDescription()));

		}
	}

	/**
	 * Test option types
	 */
	@Test
	public void testOpionType() {

		for (OptionType optionType : OptionType.values()) {
			Option option = new Option(DEFAULT_SHORT_NAME, DEFAULT_LONG_NAME,
					optionType, DEFAULT_PARAMETER_TYPE,
					DEFAULT_PARAMETER_CLASS, DEFAULT_OPTION_NAME,
					DEFAULT_LOWER_BOUNDARY, DEFAULT_UPPER_BOUNDARY,
					DEFUALT_EXPECTED_VALUES, DEFAULT_DESCRIPTION);

			assertTrue("Option type" + optionType + " was not set correctly ",
					option.getOptionType() == optionType);
		}

		Option nullOptionTypeClassOption = new Option(DEFAULT_SHORT_NAME,
				DEFAULT_LONG_NAME, null, DEFAULT_PARAMETER_TYPE,
				DEFAULT_PARAMETER_CLASS, DEFAULT_OPTION_NAME,
				DEFAULT_LOWER_BOUNDARY, DEFAULT_UPPER_BOUNDARY,
				DEFUALT_EXPECTED_VALUES, DEFAULT_DESCRIPTION);
		assertNull("Expected null option type",
				nullOptionTypeClassOption.getOptionType());
	}

	/**
	 * Test function returnValidString
	 */
	@Test
	public void testValidString() {

		String[] inputTexts = { "description", "description with spaces",
				"cammelCaseDescription", "1 23  4 5", null };
		String[] resultTexts = { "description", "description with spaces",
				"cammelCaseDescription", "1 23  4 5", "" };

		int maxIndex = Math.min(inputTexts.length, resultTexts.length);

		for (int inputTextIndex = 0; inputTextIndex < maxIndex; inputTextIndex++) {

			String validString = Option
					.returnValidString(inputTexts[inputTextIndex]);

			assertTrue(
					"Function returnValidString returns wrong result for text \""
							+ validString + "\"",
					stringEquals(resultTexts[inputTextIndex], validString));

		}

	}

	/**
	 * Test of static function returnValidStringArray()
	 */
	@Test
	public void testValidStringArray() {

		String[][] inputStringArrays = { { "a" }, { "a", "b" }, null, {},
				{ null } };
		String[][] resultStringArrays = { { "a" }, { "a", "b" }, {}, {},
				{ null } };
		int maxIndex = Math.min(inputStringArrays.length,
				resultStringArrays.length);

		for (int stringArrayIndex = 0; stringArrayIndex < maxIndex; stringArrayIndex++) {
			String[] resultValues = Option
					.returnValidStringArray(inputStringArrays[stringArrayIndex]);
			assertTrue(
					"Method resultStringArrays returns wrong result.",
					stringArraysAreEqual(resultValues,
							resultStringArrays[stringArrayIndex]));
		}
	}

	/**
	 * Test of functions setFoundOnCommandline() and isFoundOnCommandline()
	 */
	@Test
	public void testCommandLineFound() {
		Option option = createDefaultOption();
		assertFalse(
				"Option has wrong default value of \"foundOnCommandline\".",
				option.isFoundOnCommandline());
		option.setFoundOnCommandline(false);
		assertFalse(
				"Option has wrong value of \"foundOnCommandline\" - should be false",
				option.isFoundOnCommandline());
		option.setFoundOnCommandline(true);
		assertTrue(
				"Option has wrong value of \"foundOnCommandline\" - should be true",
				option.isFoundOnCommandline());
		option.setFoundOnCommandline(false);
		assertFalse(
				"Option has wrong value of \"foundOnCommandline\" - should be false (overwritten)",
				option.isFoundOnCommandline());
		option.setFoundOnCommandline(true);
		assertTrue(
				"Option has wrong value of \"foundOnCommandline\" - should be true (overwritten)",
				option.isFoundOnCommandline());
	}

	/**
	 * Test set boundary
	 */
	@Test
	public void boundaryTest() {
		Integer[] inputLowerBoundary = { -10, -1, 0, 1, 100, 10000000, null,
				Integer.MIN_VALUE, Integer.MAX_VALUE };
		Integer[] inputUpperBoundary = { -5, -1, 0, 0, 1, 100, 10000000, null,
				Integer.MIN_VALUE, Integer.MAX_VALUE };

		for (Integer lowerBoundary : inputLowerBoundary) {
			for (Integer upperBoundary : inputUpperBoundary) {

				Option option = new Option(DEFAULT_SHORT_NAME,
						DEFAULT_LONG_NAME, DEFAULT_OPTION_TYPE,
						DEFAULT_PARAMETER_TYPE, DEFAULT_PARAMETER_CLASS,
						DEFAULT_OPTION_NAME, lowerBoundary, upperBoundary,
						DEFUALT_EXPECTED_VALUES, DEFAULT_DESCRIPTION);
				assertEquals(
						String.format(
								"Lower boundary returns different value from enterd vaue  \"%d\" ",
								lowerBoundary), lowerBoundary,
						option.getLowerBound());
				assertEquals(
						String.format(
								"Lower boundary returns different value from enterd vaue  \"%d\" ",
								upperBoundary), upperBoundary,
						option.getUpperBound());

			}
		}
	}
}
